///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Microsoft Corporation. All rights reserved.
///////////////////////////////////////////////////////////////////////////////
//
// Type Library Importer utility
//
// This program imports all the types in the type library into a interop assembly
//
///////////////////////////////////////////////////////////////////////////////

using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Reflection;
using System.Reflection.Emit;
using System.Collections;
using System.Globalization;

namespace tlbimp2 {

internal class Option
{
    private String m_strName;
    private String m_strValue;

    public Option(String strName, String strValue)
    {
        m_strName = strName;
        m_strValue = strValue;
    }

    public String Name { get { return m_strName; } }
    public String Value { get { return m_strValue; } }
}

internal class Abbrevs
{
    private String[] m_aOptions;
    private bool[] m_bRequiresValue;
    private bool[] m_bCanHaveValue;

    public Abbrevs(String[] aOptions)
    {
        m_aOptions = new String[aOptions.Length];
        m_bRequiresValue = new bool[aOptions.Length];
        m_bCanHaveValue = new bool[aOptions.Length];

        // Store option list in lower case for canonical comparison.
        for (int i = 0; i < aOptions.Length; i++)
        {
            String strOption = aOptions[i].ToLower(CultureInfo.InvariantCulture);

            // A leading '*' implies the option requires a value
            // (the '*' itself is not stored in the option name).
            if (strOption.StartsWith("*"))
            {
                m_bRequiresValue[i] = true;
                m_bCanHaveValue[i] = true;
                strOption = strOption.Substring(1);
            }
            else if (strOption.StartsWith("+"))
            {
                m_bRequiresValue[i] = false;
                m_bCanHaveValue[i] = true;
                strOption = strOption.Substring(1);
            }

            m_aOptions[i] = strOption;
        }
    }

    public String Lookup(String strOpt, out bool bRequiresValue, out bool bCanHaveValue)
    {
        String strOptLower = strOpt.ToLower(CultureInfo.InvariantCulture);
        int i;
        bool bMatched = false;
        int iMatch = -1;

        // Compare option to stored list.
        for (i = 0; i < m_aOptions.Length; i++)
        {
            // Exact matches always cause immediate termination of
            // the search (else with options foo and foobar, the user
            // could never specify foo unambiguously).
            if (strOptLower.Equals(m_aOptions[i]))
            {
                bRequiresValue = m_bRequiresValue[i];
                bCanHaveValue = m_bCanHaveValue[i];
                return m_aOptions[i];
            }

            // Check for potential match (the input word is a prefix
            // of the current stored option).
            if (m_aOptions[i].StartsWith(strOptLower))
            {
                // If we've already seen a prefix match then the
                // input word is ambiguous.
                if (bMatched)
                    throw new TlbImpGeneralException(Resource.FormatString("Err_AmbigousOption", strOpt), ErrorCode.Err_AmbigousOption, true);

                // Remember this partial match.
                bMatched = true;
                iMatch = i;
            }
        }

        // If we get here with bMatched set, we saw one and only one
        // partial match, so we've got a winner.
        if (bMatched)
        {
            bRequiresValue = m_bRequiresValue[iMatch];
            bCanHaveValue = m_bCanHaveValue[iMatch];
            return m_aOptions[iMatch];
        }

        // Else the word doesn't match at all.
        throw new TlbImpGeneralException(Resource.FormatString("Err_UnknownOption", strOpt), ErrorCode.Err_UnknownOption, true);
    }
}

internal class CommandLine
{
    private String[] m_aArgList;
    private Option[] m_aOptList;
    private int m_iArgCursor;
    private int m_iOptCursor;
    private Abbrevs m_sValidOptions;

    public CommandLine(String[] aArgs, String[] aValidOpts)
    {
        int i, iArg, iOpt;

        // Keep a list of valid option names.
        m_sValidOptions = new Abbrevs(aValidOpts);

        // Temporary lists of raw arguments and options and their
        // associated values.
        String[] aArgList = new String[aArgs.Length];
        Option[] aOptList = new Option[aArgs.Length];

        // Reset counters of raw arguments and option/value pairs found
        // so far.
        iArg = 0;
        iOpt = 0;

        // Iterate through words of command line.
        for (i = 0; i < aArgs.Length; i++)
        {
            // Check for option or raw argument.
            if (aArgs[i].StartsWith("/") ||
                aArgs[i].StartsWith("-"))
            {
                String strOpt;
                String strVal = null;
                bool bRequiresValue;
                bool bCanHaveValue;

                // It's an option. Strip leading '/' or '-' and
                // anything after a value separator (':' or
                // '=').
                int iColon = aArgs[i].IndexOfAny(new char[] {':', '='});
                if (iColon == -1)
                        strOpt = aArgs[i].Substring(1);
                else
                        strOpt = aArgs[i].Substring(1, iColon - 1);

                // Look it up in the table of valid options (to
                // check it exists, get the full option name and
                // to see if an associated value is expected).
                strOpt = m_sValidOptions.Lookup(strOpt, out bRequiresValue, out bCanHaveValue);

                // Check that the user hasn't specified a value separator for an option 
                // that doesn't take a value.
                if (!bCanHaveValue && (iColon != -1))
                    throw new TlbImpGeneralException(Resource.FormatString("Err_NoValueRequired", strOpt), ErrorCode.Err_NoValueRequired, true);

                // Check that the user has put a colon if the option requires a value.
                if (bRequiresValue && (iColon == -1))
                    throw new TlbImpGeneralException(Resource.FormatString("Err_ValueRequired", strOpt), ErrorCode.Err_ValueRequired, true);
                
                // Go look for a value if there is one.
                if (bCanHaveValue && iColon != -1)
                {
                    if (iColon == (aArgs[i].Length - 1))
                    {
                        // No value separator, or
                        // separator is at end of
                        // option; look for value in
                        // next command line arg.
                        if (i + 1 == aArgs.Length)
                        {
                            throw new TlbImpGeneralException(Resource.FormatString("Err_ValueRequired", strOpt), ErrorCode.Err_ValueRequired, true);
                        }
                        else
                        {
                            if ((aArgs[i + 1].StartsWith( "/" ) || aArgs[i + 1].StartsWith( "-" )))
                                throw new TlbImpGeneralException(Resource.FormatString("Err_ValueRequired", strOpt), ErrorCode.Err_ValueRequired, true);

                            strVal = aArgs[i+1];
                            i++;
                        }
                    }
                    else
                    {
                        // Value is in same command line
                        // arg as the option, substring
                        // it out.
                        strVal = aArgs[i].Substring(iColon + 1);
                    }
                }

                // Build the option value pair.
                aOptList[iOpt++] = new Option(strOpt, strVal);
            }
            else
            {
                // Command line word is a raw argument.
                aArgList[iArg++] = aArgs[i];
            }
        }

        // Allocate the non-temporary arg and option lists at exactly
        // the right size.
        m_aArgList = new String[iArg];
        m_aOptList = new Option[iOpt];

        // Copy in the values we've calculated.
        Array.Copy(aArgList, m_aArgList, iArg);
        Array.Copy(aOptList, m_aOptList, iOpt);

        // Reset enumeration cursors to start of lists.
        m_iArgCursor = 0;
        m_iOptCursor = 0;
    }

    public int NumArgs { get { return m_aArgList.Length; } }

    public int NumOpts { get { return m_aOptList.Length; } }

    public String GetNextArg()
    {
        if (m_iArgCursor >= m_aArgList.Length)
            return null;
        return m_aArgList[m_iArgCursor++];
    }

    public Option GetNextOption()
    {
        if (m_iOptCursor >= m_aOptList.Length)
            return null;
        return m_aOptList[m_iOptCursor++];
    }
}

}